package com.uaniheng.util.matcher;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;

/**
 * 表达式验证和匹配工具
 *
 * @author gyc
 * @date 2020/5/21
 */
public class SyntaxUtils {

    private static final Map<String, Integer> KEY_WORD = new HashMap<>(2 << 2);

    private static final Map<String, Integer> BASE_OPERATOR = new HashMap<>(2 << 1);

    private static final Map<String, Integer> MULTI_OPERATOR = new HashMap<>(2 << 1);

    static {
        MULTI_OPERATOR.put("and", 2);
        MULTI_OPERATOR.put("or", 2);
        MULTI_OPERATOR.put("(", 1);
        MULTI_OPERATOR.put(")", null);

        BASE_OPERATOR.put("!=", 3);
        BASE_OPERATOR.put("=", 3);
        BASE_OPERATOR.put("like", 3);
        BASE_OPERATOR.put("!", null);

        KEY_WORD.putAll(MULTI_OPERATOR);
        KEY_WORD.putAll(BASE_OPERATOR);
    }

    /**
     * 验证表达式合法性
     * @return
     */
    public boolean check(String expression){
        return check(split(expression));
    }

    /**
     * 验证map中的数据是否匹配匹配表达式
     * @param expression
     * @return
     */
    public boolean match(String expression, Map<String, String> data){
        List<String> strList = split(expression);
        if (check(strList)){
            return buildCondition(strList).match(data);
        }
        throw new RuntimeException("表达式不合法: " + expression);

    }


    /**
     * 验证表达式是否合法
     *
     * 使用有限状态自动机判断
     */
    private boolean check(List<String> wordList) {
        //自动机状态
        int state = 0;
        //没有成对的括号数 遇到左括号+1  遇到右括号 -1
        int sum = 0;
        for (String word : wordList) {
            switch (state) {
                case 0:
                    // 0状态 输入非关键字的字符串进入1状态，输入关键字（ 进入0状态
                    if (!KEY_WORD.containsKey(word)) {
                        state = 1;
                        continue;
                    }
                    if (!"(".equals(word)) {
                        return false;
                    }
                    sum++;
                    continue;
                case 1:
                    //1状态 输入关键字 = 、!=、like将纳入2状态
                    if ("=".equals(word) || "!=".equals(word) || "like".equals(word)) {
                        state = 2;
                        continue;
                    } else {
                        return false;
                    }
                case 2:
                    //2状态 输入非关键字的字符串进入3状态
                    if (!KEY_WORD.containsKey(word)) {
                        state = 3;
                        continue;
                    } else {
                        return false;
                    }
                case 3:
                    //3状态也是结束状态 输入关键字） 进入3状态，输入关键字and、or、（ 进入0状态
                    if (")".equals(word)) {
                        sum--;
                        continue;
                    } else if ("and".equals(word) || "or".equals(word) || "(".equals(word)) {
                        state = 0;
                        if ("(".equals(word)) {
                            sum++;
                        }
                        continue;
                    } else {
                        return false;
                    }
                default:
                    return false;
            }
        }
        //当自动机为结束状态且左右括号数量一致时 返回true
        return sum == 0 && state == 3;
    }

    /**
     * 把字符串 按照 and or like = != ( ) 和空格 切分 且保留以上分隔符 不保留空格
     */
    private List<String> split(String expression) {
        List<String> result = new ArrayList<>();
        // (A=xxx or B=xxx) and C=xxx
        char[] chars = expression.toCharArray();
        StringBuilder wordBuilder = new StringBuilder();
        for (char aChar : chars) {
            if (aChar == ' ' || KEY_WORD.containsKey(String.valueOf(aChar))) {
                if (wordBuilder.length() > 0) {
                    String word = wordBuilder.toString();
                    if ("!".equals(word) && '=' == aChar) {
                        result.add("!=");
                        wordBuilder.setLength(0);
                        continue;
                    }

                    result.add(word);
                    wordBuilder.setLength(0);
                }
                if ('!' == aChar) {
                    wordBuilder.append("!");
                } else if (aChar != ' ') {
                    result.add(String.valueOf(aChar));
                }
            } else {
                wordBuilder.append(aChar);
            }
        }
        if (wordBuilder.length() > 0) {
            result.add(wordBuilder.toString());
        }
        return result;
    }


    /**
     * 生成判断条件 照搬计算器的计算逻辑
     *
     * 定义操作数栈和运算符栈 操作数： label value Condition 运算符： =  ！=  like  or  and  （  ）
     *
     * 因为Condition对象和操作数字符串都需要放入同一个栈中，所以对操作数字符串做封装在放入栈
     *
     * @param srtList 表达式拆分后的字符串列表
     * @return 最终的判断条件对象
     */
    private Condition buildCondition(List<String> srtList) {

        Stack<Element> elements = new Stack<>();
        Stack<Element> operators = new Stack<>();
        operators.push(null);

        for (String word : srtList) {
            if (KEY_WORD.containsKey(word)) {
                //如果是运算符字符串
                if ("(".equals(word)) {
                    operators.push(new BaseElement("(", ElementEnum.MULTI_OPERATOR));
                } else if (")".equals(word)) {
                    Element op;
                    while (!"(".equals((op = operators.pop()).getValueStr())) {
                        Element e1 = elements.pop();
                        Element e2 = elements.pop();
                        Condition con;
                        if (op.getStatus() == ElementEnum.BASE_OPERATOR) {
                            con = new BaseCondition(e2.getValueStr(), e1.getValueStr(),
                                    op.getValueStr());
                        } else {
                            con = new MultiCondition((Condition) e2, (Condition) e1,
                                    op.getValueStr());
                        }
                        elements.push(con);
                    }
                } else {
                    //其他运算符
                    while (getPriority(word) <= getPriority(operators.peek())) {
                        Element e1 = elements.pop();
                        Element e2 = elements.pop();
                        Element op = operators.pop();
                        Condition con;
                        if (op.getStatus() == ElementEnum.BASE_OPERATOR) {
                            con = new BaseCondition(e2.getValueStr(), e1.getValueStr(),
                                    op.getValueStr());
                        } else {
                            con = new MultiCondition((Condition) e2, (Condition) e1,
                                    op.getValueStr());
                        }
                        elements.push(con);
                    }
                    if (BASE_OPERATOR.containsKey(word)) {
                        operators.push(new BaseElement(word, ElementEnum.BASE_OPERATOR));
                    } else {
                        operators.push(new BaseElement(word, ElementEnum.MULTI_OPERATOR));
                    }
                }
            } else {
                //非运算符的基本字符串 入栈
                elements.push(new BaseElement(word, ElementEnum.BASE_STR));
            }
        }
        while (operators.peek() != null) {
            Element e1 = elements.pop();
            Element e2 = elements.pop();
            Element op = operators.pop();
            Condition con;
            if (op.getStatus() == ElementEnum.BASE_OPERATOR) {
                con = new BaseCondition(e2.getValueStr(), e1.getValueStr(), op.getValueStr());
            } else {
                con = new MultiCondition((Condition) e2, (Condition) e1, op.getValueStr());
            }
            elements.push(con);
        }
        return (Condition) elements.pop();
    }


    /**
     * 根据表达式创建一个Condition
     * 可以重复使用
     * @param expression
     * @return
     */
    public Condition buildCondition(String expression){
        List<String> strList = split(expression);
        if (check(strList)){
            return buildCondition(strList);
        }
        throw new RuntimeException("表达式不合法: " + expression);
    }
    /**
     * 获取字符串运算符的优先级
     */
    private static int getPriority(String op) {
        if (op == null) {
            return 0;
        }
        return KEY_WORD.get(op);
    }

    /**
     * 获取封装在Element对象中的运算符的优先级
     */
    private static int getPriority(Element element) {
        if (element == null) {
            return 0;
        }
        return KEY_WORD.get(element.getValueStr());
    }

    /**
     * test
     */
    public static void main(String[] args) {
        // ((Aand!=xxx or B like xxx)and C=xxx)and (aaa= iii)
        HashMap<String, String> map = new HashMap<String, String>() {
            {
                put("label1", "xxx");
                put("label2", "22xxx2");
                put("label3", "xxx");
                put("label4", "iii");
            }
        };

        SyntaxUtils syntaxUtils = new SyntaxUtils();

        String expression = "((label1 !=xxx or label2 like xxx)and label3=xxx)and (label4= iii)";
        //System.out.println(syntaxUtils.check(expression));

        long start = System.currentTimeMillis();
        for (int i = 0; i < 100000; i ++){
            syntaxUtils.match(expression, map);
        }
        long end = System.currentTimeMillis() - start;
        System.out.println(end);

        /**
         * 如果 match(express,map) 方法在一个表达式下频繁调用
         * 可以先build Condition，使用Condition.match(Map)方法
         */
        Condition condition = syntaxUtils.buildCondition(expression);
        start = System.currentTimeMillis();
        for (int i = 0; i < 100000; i ++){
            condition.match(map);
        }
        end = System.currentTimeMillis() - start;
        System.out.println(end);
    }

}
